/*
    keyboard driver
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    All functions copied from qemu 0.13 pckbd.c

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "../ioport.h"

/* debug PC keyboard */
//#define DEBUG_KBD
#ifdef DEBUG_KBD
#define DPRINTF(fmt, ...)                                       \
    do { printf("KBD: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...)
#endif

/*	Keyboard Controller Commands */
#define KBD_CCMD_READ_MODE	0x20	/* Read mode bits */
#define KBD_CCMD_WRITE_MODE	0x60	/* Write mode bits */
#define KBD_CCMD_GET_VERSION	0xA1	/* Get controller version */
#define KBD_CCMD_MOUSE_DISABLE	0xA7	/* Disable mouse interface */
#define KBD_CCMD_MOUSE_ENABLE	0xA8	/* Enable mouse interface */
#define KBD_CCMD_TEST_MOUSE	0xA9	/* Mouse interface test */
#define KBD_CCMD_SELF_TEST	0xAA	/* Controller self test */
#define KBD_CCMD_KBD_TEST	0xAB	/* Keyboard interface test */
#define KBD_CCMD_KBD_DISABLE	0xAD	/* Keyboard interface disable */
#define KBD_CCMD_KBD_ENABLE	0xAE	/* Keyboard interface enable */
#define KBD_CCMD_READ_INPORT    0xC0    /* read input port */
#define KBD_CCMD_READ_OUTPORT	0xD0    /* read output port */
#define KBD_CCMD_WRITE_OUTPORT	0xD1    /* write output port */
#define KBD_CCMD_WRITE_OBUF	0xD2
#define KBD_CCMD_WRITE_AUX_OBUF	0xD3    /* Write to output buffer as if
					   initiated by the auxiliary device */
#define KBD_CCMD_WRITE_MOUSE	0xD4	/* Write the following byte to the mouse */
#define KBD_CCMD_DISABLE_A20    0xDD    /* HP vectra only ? */
#define KBD_CCMD_ENABLE_A20     0xDF    /* HP vectra only ? */
#define KBD_CCMD_RESET	        0xFE

/* Keyboard Commands */
#define KBD_CMD_SET_LEDS	0xED	/* Set keyboard leds */
#define KBD_CMD_ECHO     	0xEE
#define KBD_CMD_GET_ID 	        0xF2	/* get keyboard ID */
#define KBD_CMD_SET_RATE	0xF3	/* Set typematic rate */
#define KBD_CMD_ENABLE		0xF4	/* Enable scanning */
#define KBD_CMD_RESET_DISABLE	0xF5	/* reset and disable scanning */
#define KBD_CMD_RESET_ENABLE   	0xF6    /* reset and enable scanning */
#define KBD_CMD_RESET		0xFF	/* Reset */

/* Keyboard Replies */
#define KBD_REPLY_POR		0xAA	/* Power on reset */
#define KBD_REPLY_ACK		0xFA	/* Command ACK */
#define KBD_REPLY_RESEND	0xFE	/* Command NACK, send the cmd again */

/* Status Register Bits */
#define KBD_STAT_OBF 		0x01	/* Keyboard output buffer full */
#define KBD_STAT_IBF 		0x02	/* Keyboard input buffer full */
#define KBD_STAT_SELFTEST	0x04	/* Self test successful */
#define KBD_STAT_CMD		0x08	/* Last write was a command write (0=data) */
#define KBD_STAT_UNLOCKED	0x10	/* Zero if keyboard locked */
#define KBD_STAT_MOUSE_OBF	0x20	/* Mouse output buffer full */
#define KBD_STAT_GTO 		0x40	/* General receive/xmit timeout */
#define KBD_STAT_PERR 		0x80	/* Parity error */

/* Controller Mode Register Bits */
#define KBD_MODE_KBD_INT	0x01	/* Keyboard data generate IRQ1 */
#define KBD_MODE_MOUSE_INT	0x02	/* Mouse data generate IRQ12 */
#define KBD_MODE_SYS 		0x04	/* The system flag (?) */
#define KBD_MODE_NO_KEYLOCK	0x08	/* The keylock doesn't affect the keyboard if set */
#define KBD_MODE_DISABLE_KBD	0x10	/* Disable keyboard interface */
#define KBD_MODE_DISABLE_MOUSE	0x20	/* Disable mouse interface */
#define KBD_MODE_KCC 		0x40	/* Scan code conversion to PC format */
#define KBD_MODE_RFU		0x80

#define KBD_QUEUE_SIZE 256

typedef struct {
	uint8_t data[KBD_QUEUE_SIZE];
	int rptr, wptr, count;
} KBDQueue;

typedef struct KBDState {
	uint8_t write_cmd; /* if non zero, write data to port 60 is expected */
	uint8_t status;
	uint8_t mode;
//	void *kbd;
//	void *mouse;
	KBDQueue queues[2];

	/* keyboard state */
	int kbd_write_cmd;
	int scan_enabled;
} KBDState;

static uint32_t kbd_read_status(void *opaque, uint32_t addr)
{
	KBDState *s = opaque;
	int val;
	val = s->status;
	DPRINTF("kbd: read status=0x%02x\n", val);
	return val;
}

static void kbd_update_irq(KBDState *s)
{
	s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF);
	if (s->queues[0].count != 0 ||
		s->queues[1].count != 0) {
		s->status |= KBD_STAT_OBF;
		if (s->queues[1].count != 0) {
			s->status |= KBD_STAT_MOUSE_OBF;
		} else {
		}
	}
}

static void kbd_queue(KBDState *s, int b, int aux)
{
	KBDQueue *q = &s->queues[aux];
	
	if (q->count >= KBD_QUEUE_SIZE)
		return;
	q->data[q->wptr] = b;
	if (++q->wptr == KBD_QUEUE_SIZE)
		q->wptr = 0;
	q->count++;
	kbd_update_irq(s);
}

static void kbd_write_command(void *opaque, uint32_t addr, uint32_t val)
{
	KBDState *s = opaque;
	
	DPRINTF("kbd: write cmd=0x%02x\n", val);

	switch(val) {
	case KBD_CCMD_READ_MODE:
		kbd_queue(s, s->mode, 0);
		break;
	case KBD_CCMD_WRITE_MODE:
	case KBD_CCMD_WRITE_OBUF:
	case KBD_CCMD_WRITE_AUX_OBUF:
	case KBD_CCMD_WRITE_MOUSE:
	case KBD_CCMD_WRITE_OUTPORT:
		s->write_cmd = val;
		break;
	case KBD_CCMD_MOUSE_DISABLE:
		s->mode |= KBD_MODE_DISABLE_MOUSE;
		break;
	case KBD_CCMD_MOUSE_ENABLE:
		s->mode &= ~KBD_MODE_DISABLE_MOUSE;
		break;
	case KBD_CCMD_TEST_MOUSE:
		kbd_queue(s, 0x00, 0);
		break;
	case KBD_CCMD_SELF_TEST:
		s->status |= KBD_STAT_SELFTEST;
		kbd_queue(s, 0x55, 0);
		break;
	case KBD_CCMD_KBD_TEST:
		kbd_queue(s, 0x00, 0);
		break;
	case KBD_CCMD_KBD_DISABLE:
		s->mode |= KBD_MODE_DISABLE_KBD;
		kbd_update_irq(s);
		break;
	case KBD_CCMD_KBD_ENABLE:
		s->mode &= ~KBD_MODE_DISABLE_KBD;
		kbd_update_irq(s);
		break;
#if 0
	case KBD_CCMD_READ_INPORT:
		kbd_queue(s, 0x00, 0);
		break;
	case KBD_CCMD_READ_OUTPORT:
		kbd_queue(s, s->outport, 0);
		break;
	case KBD_CCMD_ENABLE_A20:
		if (s->a20_out) {
		qemu_irq_raise(*s->a20_out);
		}
		s->outport |= KBD_OUT_A20;
		break;
	case KBD_CCMD_DISABLE_A20:
		if (s->a20_out) {
		qemu_irq_lower(*s->a20_out);
		}
		s->outport &= ~KBD_OUT_A20;
		break;
	case KBD_CCMD_RESET:
		qemu_system_reset_request();
		break;
	case 0xff:
		/* ignore that - I don't know what is its use */
		break;
#endif
	default:
		fprintf(stderr, "%s: unsupported keyboard cmd=0x%02x\n", __func__, val);
		break;
	}
}

static uint32_t kbd_read_data(void *opaque, uint32_t addr)
{
	KBDState *s = opaque;
	uint32_t val;
	KBDQueue *q;
	int index;

	q = &s->queues[0]; /* first check KBD data */
	if (q->count == 0)
		q = &s->queues[1]; /* then check AUX data */
	if (q->count == 0) {
		/* NOTE: if no data left, we return the last keyboard one
		(needed for EMM386) */
		/* XXX: need a timer to do things correctly */
		q = &s->queues[0];
		index = q->rptr - 1;
		if (index < 0)
			index = KBD_QUEUE_SIZE - 1;
		val = q->data[index];
	} else {
		val = q->data[q->rptr];
		if (++q->rptr == KBD_QUEUE_SIZE)
			q->rptr = 0;
		q->count--;
		/* reading deasserts IRQ */

	}
	/* reassert IRQs if data left */

	DPRINTF("kbd: read data=0x%02x\n", val);

	return val;
}

static void kbd_write_keyboard(KBDState *s, int val)
{
	switch(s->kbd_write_cmd) {
	default:
	case -1:
		switch(val) {
		case 0x00:
			kbd_queue(s, KBD_REPLY_ACK, 0);
		break;
		case 0x05:
			kbd_queue(s, KBD_REPLY_RESEND, 0);
		break;
		case KBD_CMD_GET_ID:
			kbd_queue(s, KBD_REPLY_ACK, 0);
			kbd_queue(s, 0xab, 0);
			kbd_queue(s, 0x83, 0);
			break;
		case KBD_CMD_ECHO:
			kbd_queue(s, KBD_CMD_ECHO, 0);
			break;
		case KBD_CMD_ENABLE:
			s->scan_enabled = 1;
			kbd_queue(s, KBD_REPLY_ACK, 0);
			break;
		case KBD_CMD_SET_LEDS:
		case KBD_CMD_SET_RATE:
			s->kbd_write_cmd = val;
			kbd_queue(s, KBD_REPLY_ACK, 0);
			break;
		case KBD_CMD_RESET_DISABLE:
			s->scan_enabled = 0;
			kbd_queue(s, KBD_REPLY_ACK, 0);
			break;
		case KBD_CMD_RESET_ENABLE:
//			kbd_reset_keyboard(s);
			s->scan_enabled = 1;
			kbd_queue(s, KBD_REPLY_ACK, 0);
			break;
		case KBD_CMD_RESET:
//			kbd_reset_keyboard(s);
			kbd_queue(s, KBD_REPLY_ACK, 0);
			kbd_queue(s, KBD_REPLY_POR, 0);
			break;
		default:
			kbd_queue(s, KBD_REPLY_ACK, 0);
			break;
		}
		break;
	case KBD_CMD_SET_LEDS:
		kbd_queue(s, KBD_REPLY_ACK, 0);
		s->kbd_write_cmd = -1;
		break;
	case KBD_CMD_SET_RATE:
		kbd_queue(s, KBD_REPLY_ACK, 0);
		s->kbd_write_cmd = -1;
		break;
	}
}

static void kbd_write_data(void *opaque, uint32_t addr, uint32_t val)
{
	KBDState *s = opaque;
	
	DPRINTF("kbd: write data=0x%02x\n", val);
	
	switch(s->write_cmd) {
	case 0:
		kbd_write_keyboard(s, val);
		break;
	case KBD_CCMD_WRITE_MODE:
		s->mode = val;
		kbd_update_irq(s);
		break;
#if 0
	case KBD_CCMD_WRITE_OBUF:
		kbd_queue(s, val, 0);
		break;
	case KBD_CCMD_WRITE_AUX_OBUF:
		kbd_queue(s, val, 1);
		break;
	case KBD_CCMD_WRITE_OUTPORT:
		ioport92_write(s, 0, val);
		break;
	case KBD_CCMD_WRITE_MOUSE:
		ps2_write_mouse(s->mouse, val);
		break;
#endif
	default:
		break;
	}
	s->write_cmd = 0;
}

static void kbd_reset(void *opaque)
{
	int i;
	KBDState *s = opaque;
	KBDQueue *q;

	s->kbd_write_cmd = -1;
	s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
	s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
//	s->outport = KBD_OUT_RESET | KBD_OUT_A20;
	for(i = 0; i < 2; i++) {
		q = &s->queues[i];
		q->rptr = 0;
		q->wptr = 0;
		q->count = 0;
	}
}

void kbd_init(void)
{
	KBDState *s;
	s = calloc(1, sizeof(KBDState));

	kbd_reset(s);
	register_ioport_read(0x60, 1, 1, kbd_read_data, s);
	register_ioport_write(0x60, 1, 1, kbd_write_data, s);
	register_ioport_read(0x64, 1, 1, kbd_read_status, s);
	register_ioport_write(0x64, 1, 1, kbd_write_command, s);
}